#! /usr/bin/env python
# coding: utf-8

"""
[About]
...

[License]
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

__author__ = ['Bianca Munaro (biacsf@gmail.com)',
                     'Davi Vercillo (davivercillo@gmail.com)',
                     'Diego Xavier (diegoxavier@ufrj.br)',
                     'Patricia Zúdio (patriciazudio@ufrj.br)',
                     'Vinícius Ribeiro (vl.ribeiro@ufrj.br)']

__version__ = "1.0.1"

import os
import sys
import copy

try:
    import pygame
except:
    print "Cannot found PyGame library. More information at http://www.pygame.org/news.html ."
    sys.exit(-1)

try:
    import psyco
    psyco.full()
except:
    print "Cannot use JIT (Just in Time Compiler). More information at http://psyco.sourceforge.net ."


#------------------------------------------------------------------------------

class MenuScreen:
    """This Class implements the Title Screen.
    """
    def __init__(self, screen, sounds):
        """Contructor.
        """
        self.state = True
        self.action = 0
        self.screen = screen
        self.sounds = sounds
        self.background_off01 = pygame.image.load("data" + os.sep + "bg_menu_off01.png")
        self.background_off02 = pygame.image.load("data" + os.sep + "bg_menu_off02.png")
        self.background_on =  pygame.image.load("data" + os.sep + "bg_menu_on.png")
        
    def draw(self):
        """This method draws in the Screen.
        """
        self.screen.fill((255,255,255))

        if self.state == True:
            self.screen.blit(self.background_off01, (0,0))
            self.state = False
        else:
            self.screen.blit(self.background_off02, (0,0))
            self.state = True
            
        pos = pygame.mouse.get_pos()
            
        if 20 <= pos[0] <= 320 and 608 <= pos[1] <= 718:
            self.screen.blit(self.background_on, (0,586), ((0,0),(325,190)))
        elif 350 <= pos[0] <= 560 and 698 <= pos[1] <= 748:
            self.screen.blit(self.background_on, (335,676), ((335,90),(243,82)))
        elif 600 <= pos[0] <= 765 and 618 <= pos[1] <= 658:
            self.screen.blit(self.background_on, (570,586), ((570,0),(210,85)))
        elif 790 <= pos[0] <= 980 and 683 <= pos[1] <= 758:
            self.screen.blit(self.background_on, (763,661), ((763,75),(1024,120)))
            
        pygame.display.flip()
    
    def start(self):
        """.
        """  
        self.action = 0
        
        while True:
            pygame.time.wait(80)
            self.draw()         
            for e in pygame.event.get():
                if e.type == pygame.MOUSEBUTTONDOWN:
                    if 20 <= e.pos[0] <= 320 and 608 <= e.pos[1] <= 718:
                        self.action = 1
                        self.sounds['click'].play()
                        return
                    elif 350 <= e.pos[0] <= 560 and 698 <= e.pos[1] <= 748:
                        self.action = 3
                        self.sounds['click'].play()
                        return
                    elif 600 <= e.pos[0] <= 765 and 618 <= e.pos[1] <= 658:
                        self.action = 2
                        self.sounds['click'].play()
                        return
                    elif 790 <= e.pos[0] <= 980 and 683 <= e.pos[1] <= 758:
                        self.action = 4
                        self.sounds['click'].play()
                        return
                elif e.type == pygame.QUIT:
                    self.action = 4
                    return

#------------------------------------------------------------------------------

class GameScreen:
    """Game class.
    """
    def __init__(self, screen, configuration, sounds):
        """Constructor.
        """
        self.cell_size = 80
        self.screen = screen
        self.action = 0
        self.difficulty = configuration['difficulty']
        self.ab_cut_off = configuration['a-b cut-off']
        self.sounds = sounds
        self.end = (False, None)
        
        self.font = pygame.font.Font(None, 36)
        self.possible_move_sprite = pygame.image.load("data" + os.sep + "sprite_possmove.png")
        
        self.background_off = pygame.image.load("data" + os.sep + "bg_game_off.png")
        self.background_on = pygame.image.load("data" + os.sep + "bg_game_on.png")
        
        self.human_marker = -1
        self.human_sprite = pygame.image.load("data" + os.sep + "sprite_blue.png")
        self.computer_marker = 1
        self.computer_sprite = pygame.image.load("data" + os.sep + "sprite_red.png")
        
        translator = {'Easy':1, 'Medium':3, 'Hard':5}
        self.depth = translator[self.difficulty]
        self.score = (2,2) 
        self.table = [[0,0,0,0,0,0,0,0], 
                             [0,0,0,0,0,0,0,0], 
                             [0,0,0,0,0,0,0,0], 
                             [0,0,0,self.human_marker,self.computer_marker,0,0,0], 
                             [0,0,0,self.computer_marker,self.human_marker,0,0,0], 
                             [0,0,0,0,0,0,0,0], 
                             [0,0,0,0,0,0,0,0], 
                             [0,0,0,0,0,0,0,0]]   
        
    def possible_moves(self, table, player):
        """Method that search the next possible moves.
        """
        possible_moves = []
        
        if player == self.human_marker:
            oponent = self.computer_marker
        else:
            oponent = self.human_marker
        
        for row in range(0, 8):
            for column in range(0, 8):
                if table[row][column] == 0:
                    for walk_row, walk_column in [(1,1), (-1,-1), (1,-1), (-1,1), (0,1), (0,-1), (1,0), (-1,0)]:
                        force_stop = False
                        if 0 <= row+walk_row <= 7 and 0 <= column+walk_column <= 7 and table[row+walk_row][column+walk_column] == oponent:
                            temp_row, temp_column = row + 2*walk_row, column + 2*walk_column
                            while 0 <= temp_row <= 7 and 0 <= temp_column <= 7 and table[temp_row][temp_column] != 0:
                                if table[temp_row][temp_column] == player:
                                    possible_moves += [(row, column)]
                                    force_stop = True
                                    break
                                temp_row, temp_column = temp_row + walk_row, temp_column + walk_column
                        if force_stop: break
                        
        return possible_moves
    
    def evaluation_function(self, table):
        """The evaluation function: Disk-square tables.
        http://radagast.se/othello/howto.html
        """
        model = [[200,-100,15,10,10,15,-100,200], 
                       [-100,-100,15,10,10,15,-100,-100], 
                       [15,15,15,10,10,15,15,15], 
                       [10,10,10,05,05,10,10,10], 
                       [10,10,10,05,05,10,10,10], 
                       [15,15,15,10,10,15,15,15], 
                       [-100,-100,15,10,10,15,-100,-100],
                       [200,-100,15,10,10,15,-100,200]]
        
        evaluation = 0
        for row in range(8):
            for column in range(8):
                evaluation += table[row][column] * model[row][column]
                    
        return evaluation
    
    
    def min_max(self, table, depth):
        """.
        """
        if depth != 0 and depth %2 == 0:
            moves = self.possible_moves(table, self.computer_marker)
            opt = []
            
            if moves:
                for row, column in moves:
                    temp_table = copy.deepcopy(table)
                    temp_table[row][column] = self.computer_marker
                    opt += [self.min_max(temp_table, depth-1)]
                return min(opt)
            else:
                return
            
        elif depth != 0 and depth %2 == 1:
            moves = self.possible_moves(table, self.human_marker)
            opt = []
            
            if moves:
                for row, column in moves:
                    temp_table = copy.deepcopy(table)
                    temp_table[row][column] = self.human_marker
                    opt += [self.min_max(temp_table, depth-1)]
                return max(opt)
            else:
                return
            
        elif depth == 0:
            return self.evaluation_function(table)
    
    def min_max_ab(self, table, depth, alpha=-1000000, beta=1000000):
        """.
        """
        if depth != 0 and depth %2 == 0:
            moves = self.possible_moves(table, self.computer_marker)
            opt = []
            
            if moves:
                for row, column in moves:
                    if alpha < beta:
                        temp_table = copy.deepcopy(table)
                        temp_table[row][column] = self.computer_marker
                        opt += [self.min_max_ab(temp_table, depth-1, alpha, beta)]
                        minimo = min(opt)
                        if minimo<beta:
                            beta = minimo
                    else:
                        break
                return min(opt)
            else:
                return
            
        elif depth != 0 and depth %2 == 1:
            moves = self.possible_moves(table, self.human_marker)
            opt = []
            
            if moves:
                for row, column in moves:
                    if alpha<beta:
                        temp_table = copy.deepcopy(table)
                        temp_table[row][column] = self.human_marker
                        opt += [self.min_max_ab(temp_table, depth-1, alpha, beta)]
                        maximo = max(opt)
                        if maximo>alpha:
                            alpha = maximo
                    else:
                        break
                return max(opt)
            else:
                return
            
        elif depth == 0:
            return self.evaluation_function(table)        
        
    def player_move(self,position):
        """Method that execute the player moviment.
        """
        column = (position[0] - 70) // self.cell_size
        row = (position[1] - 64) // self.cell_size
        
        moves = self.possible_moves(self.table, self.human_marker)
        
        if moves and ((row, column) in moves):
            self.sounds['move'].play()
            self.table[row][column] = self.human_marker
            self.update_table((row, column))
            self.draw()
            return True
        else:
            return False
    
    def computer_move(self, depth):
        """Method that execute the computer moviment"""
        self.draw_message("Thinking...")
        moves = self.possible_moves(self.table, self.computer_marker)
        opts = []
        if moves:
            for row, column in moves:
                temp_table = copy.deepcopy(self.table)
                temp_table[row][column] = self.computer_marker
                if self.ab_cut_off == 'Enabled':
                    opts += [(self.min_max_ab(temp_table, depth-1), (row, column))]
                else:
                    opts += [(self.min_max(temp_table, depth-1), (row, column))]
            row, column = max(opts)[1]
            self.sounds['move'].play()
            self.table[row][column] = self.computer_marker
            self.update_table((row, column))

    def update_table(self, position):
        """Method that update the chips on the board, after the move defined in 'position'.
        """
        row, column = position[0], position[1]
        player = self.table[row][column]
        if player == self.human_marker:
            oponent = self.computer_marker
        else:
            oponent = self.human_marker
        for walk_row, walk_column in [(1,1), (-1,-1), (1,-1), (-1,1), (0,1), (0,-1), (1,0), (-1,0)]:
            if 0 <= row+walk_row <= 7 and 0 <= column+walk_column <= 7 and self.table[row+walk_row][column+walk_column] == oponent:
                temp_row, temp_column = row + walk_row, column + walk_column
                while 0 <= temp_row <= 7 and 0 <= temp_column <= 7 and self.table[temp_row][temp_column] != 0:
                    if self.table[temp_row][temp_column] == player:
                        while temp_row != row or temp_column != column:
                            self.table[temp_row][temp_column] = player
                            temp_row, temp_column = temp_row - walk_row, temp_column - walk_column
                        break
                    temp_row, temp_column = temp_row + walk_row, temp_column + walk_column
        self.calculate_score()    
                        
    def calculate_score(self):
        """Method that calculate the score."""
        empty_space = player_space = computer_space = 0
        for line in self.table:
            empty_space += line.count(0)
            player_space += line.count(self.human_marker)
            computer_space += line.count(self.computer_marker)
        self.score = (player_space, computer_space)
        
        if not (self.possible_moves(self.table, self.human_marker) or self.possible_moves(self.table, self.computer_marker)):
            if player_space > computer_space:
                self.end = (True, "You win !")
            elif player_space < computer_space:
                self.end = (True, "You lose !")
            else:
                self.end = (True, "Draw !")
                
    def draw(self, moves=False):
        """Method that scan the board and update the screen image.
        """
            
        translator  = {self.human_marker:self.human_sprite, self.computer_marker:self.computer_sprite}
        self.screen.fill((255, 255, 255))
        self.screen.blit(self.background_off, (0,0))

        pos = pygame.mouse.get_pos()
        if 888 <= pos[0] <= 962 and 529 <= pos[1] <= 556:
            self.screen.blit(self.background_on, (825,516), ((0,0), (199, 53)))
        elif 895 <= pos[0] <= 954 and 580 <= pos[1] <= 606:
            self.screen.blit(self.background_on, (825,569), ((0,53), (199, 48)))
        elif 903 <= pos[0] <= 947 and 630 <= pos[1] <= 656:
            self.screen.blit(self.background_on, (825,622), ((0,106), (199, 55)))
            
        if self.end[0]:
            self.screen.blit(self.font.render(self.end[1], 1, (255, 255, 255)), (850, 468))
        
        for row in range(8):
            for column in range(8):
                if self.table[row][column]:
                    table_position = (column*self.cell_size + 70, row*self.cell_size + 64)
                    self.screen.blit(translator[self.table[row][column]], table_position)
                    
        if moves:
            for row,column in self.possible_moves(self.table, self.human_marker):
                table_position = (column*self.cell_size + 70, row*self.cell_size + 64)
                self.screen.blit(self.possible_move_sprite, table_position)
        
        self.screen.blit(self.font.render("%d" % self.score[0], 1, (255, 255, 255)), (950, 173))
        self.screen.blit(self.font.render("%d" % self.score[1], 1, (255, 255, 255)), (950, 263))
        self.screen.blit(self.font.render(self.difficulty, 1, (255, 255, 255)), (850, 345))
        self.screen.blit(self.font.render(self.ab_cut_off, 1, (255, 255, 255)), (850, 408))
        
        pygame.display.flip()
        
    def draw_message(self, string):
        self.screen.blit(self.font.render(string, 1, (255, 255, 255)), (850, 468))
        pygame.display.flip()
        
    def start(self):
        """Method that start the game."""
        self.restart()
        while True:
            self.draw(True)
            pygame.time.wait(20)
            if not self.possible_moves(self.table, self.human_marker) and self.possible_moves(self.table, self.computer_marker):
                self.computer_move(self.depth)
            for event in pygame.event.get(): 
                if event.type == pygame.QUIT:
                    self.action = 4
                    return
                elif event.type == pygame.MOUSEBUTTONDOWN:
                    if 70 <= event.pos[0] <= 710 and 64 <= event.pos[1] <= 704:
                        if not self.end[0] and self.player_move(event.pos):
                            self.computer_move(self.depth)
                    elif 888 <= event.pos[0] <= 962 and 529 <= event.pos[1] <= 556:
                        self.restart()
                        self.sounds['click'].play()
                    elif 895 <= event.pos[0] <= 954 and 580 <= event.pos[1] <= 606:
                        self.action = 0
                        self.sounds['click'].play()
                        return
                    elif 903 <= event.pos[0] <= 947 and 630 <= event.pos[1] <= 656:
                        self.action = 4
                        self.sounds['click'].play()
                        return
    
    def restart(self):
        """Method that restart the game. """
        self.end = (False, None)
        self.score = (2,2)
        self.table = [[0,0,0,0,0,0,0,0], 
                             [0,0,0,0,0,0,0,0], 
                             [0,0,0,0,0,0,0,0], 
                             [0,0,0,self.human_marker,self.computer_marker,0,0,0], 
                             [0,0,0,self.computer_marker,self.human_marker,0,0,0], 
                             [0,0,0,0,0,0,0,0], 
                             [0,0,0,0,0,0,0,0], 
                             [0,0,0,0,0,0,0,0]]
        self.draw()

#------------------------------------------------------------------------------

class OptionsScreen:
    """Class that implement the Option Screen.
    """
    def __init__(self, screen, configuration, sounds):
        """Constructor.
        """
        self.screen = screen
        self.action = 0
        self.background_off = pygame.image.load("data" + os.sep + "bg_options_off.png")
        self.back_on = pygame.image.load("data" + os.sep + "bg_back_on.png")
        self.sprite_blue = pygame.image.load("data" + os.sep + "sprite_blue_option.png")
        self.sprite_red = pygame.image.load("data" + os.sep + "sprite_red_option.png")
        self.configuration = configuration
        self.sounds = sounds
        
    def draw(self):
        """Method that draw rectangles. It won't be necessary in the future.
        """
        self.screen.fill((125,125,125))
        self.screen.blit(self.background_off, (0,0))
        
        if self.configuration['sound'] == 'on':
            self.screen.blit(self.sprite_blue, (495, 168))
        else:
            self.screen.blit(self.sprite_blue, (590, 168))
        
        if self.configuration['difficulty'] == 'Easy':
            self.screen.blit(self.sprite_red, (391, 268))
        elif self.configuration['difficulty'] == 'Medium':
            self.screen.blit(self.sprite_red, (511, 268))
        else:
            self.screen.blit(self.sprite_red, (683, 268))
            
        if self.configuration['a-b cut-off'] == 'Enabled':
            self.screen.blit(self.sprite_blue, (527, 368))
        else:
            self.screen.blit(self.sprite_blue, (612, 368))
        
        pos = pygame.mouse.get_pos()
        if 362 <= pos[0] <= 662 and 563 <= pos[1] <= 603:
            self.screen.blit(self.back_on, (0, 537))
        
        pygame.display.flip()
    
    def start(self):
        """Method that start the Option Screen, capturing the event and defining the action.
        """  
        while True:
            pygame.time.wait(20)
            self.draw()
            for e in pygame.event.get():
                if e.type == pygame.MOUSEBUTTONDOWN:
                    if 362 <= e.pos[0] <= 662 and 563 <= e.pos[1] <= 603:
                        self.action = 0
                        self.sounds['click'].play()
                        return
                    elif 520 <= e.pos[0] <= 570 and 160 <= e.pos[1] <= 190:
                        self.configuration['sound'] = 'on'
                        self.sounds['music menu'].play(-1)
                        self.sounds['click'].play()
                    elif 616 <= e.pos[0] <= 666 and 160 <= e.pos[1] <= 190:
                        self.configuration['sound'] = 'off'
                        pygame.mixer.stop()
                        self.sounds['click'].play()
                    elif 415 <= e.pos[0] <= 490 and 263 <= e.pos[1] <= 293:
                        self.configuration['difficulty'] = 'Easy'
                        self.sounds['click'].play()
                    elif 535 <= e.pos[0] <= 610 and 263 <= e.pos[1] <= 293:
                        self.configuration['difficulty'] = 'Medium'
                        self.sounds['click'].play()
                    elif 712 <= e.pos[0] <= 787 and 263 <= e.pos[1] <= 293:
                        self.configuration['difficulty'] = 'Hard'
                        self.sounds['click'].play()
                    elif 550 <= e.pos[0] <= 600 and 363 <= e.pos[1] <= 393:
                        self.configuration['a-b cut-off'] = 'Enabled'
                        self.sounds['click'].play()
                    elif 638 <= e.pos[0] <= 688 and 363 <= e.pos[1] <= 393:
                        self.configuration['a-b cut-off'] = 'Disabled'
                        self.sounds['click'].play()
                elif e.type == pygame.QUIT:
                    self.action = 4
                    return

#------------------------------------------------------------------------------

class AboutScreen:
    """Class that implement the score board.
    """
    def __init__(self, screen, sounds):
        """Constructor.
        """
        self.screen = screen
        self.action = 0
        self.background_off = pygame.image.load("data" + os.sep + "bg_about_off.png")
        self.back_on = pygame.image.load("data" + os.sep + "bg_back_on.png")
        self.sounds = sounds
    
    def draw(self):
        """Method that draw rectangles. It won't be necessary in the future.
        """
        self.screen.fill((125,125,125))
        self.screen.blit(self.background_off, (0,0))

        pos = pygame.mouse.get_pos()
        if 362 <= pos[0] <= 662 and 563 <= pos[1] <= 603:
            self.screen.blit(self.back_on, (0, 537))
        
        pygame.display.flip()
        
        
    
    def start(self):
        """Method that start the Score Board Screen, capturing the event and defining the action.
        """   
        while True:
            pygame.time.wait(20)
            self.draw()
            for e in pygame.event.get():
                if e.type == pygame.MOUSEBUTTONDOWN and 362 <= e.pos[0] <= 662 and 563 <= e.pos[1] <= 603:
                    self.action = 0
                    self.sounds['click'].play()
                    return
                elif e.type == pygame.QUIT:
                    self.action = 4
                    return

#------------------------------------------------------------------------------

class Game:
    """Control Class.
    """
    def __init__(self):
        """Constructor.
        """
        try:
            pygame.init()
            pygame.font.init()
            pygame.mixer.init()
        except pygame.fastevent:
            print 'Cannot init pygame modules. Sorry...'
            sys.exit(-1)
        
        self.sounds = { 'click': pygame.mixer.Sound("data" + os.sep + "sound_click.ogg"),
                                  'move': pygame.mixer.Sound("data" + os.sep + "sound_move.ogg"),
                                  'music menu': pygame.mixer.Sound("data" + os.sep + "sound_music_menu.ogg"),
                                  'music game': pygame.mixer.Sound("data" + os.sep + "sound_music_game.ogg")}
        self.window = pygame.display.set_mode((1024, 768), pygame.FULLSCREEN)
        self.screen = pygame.display.get_surface()
        self.configuration = {'sound':'on',
                                          'difficulty':'Medium',
                                          'a-b cut-off':'Enabled'}
        
    def start(self):
        """Method that start the Game
        """
        menu_screen = MenuScreen(self.screen, self.sounds)    
        self.sounds['music menu'].play(-1)
        menu_screen.start()
        action = menu_screen.action
        
        while True:
            pygame.time.wait(20)
            if action == 1:
                self.sounds['music menu'].stop()
                if self.configuration['sound'] == 'on':
                    self.sounds['music game'].play(-1)
                game_screen = GameScreen(self.screen, self.configuration, self.sounds)
                game_screen.start()
                action = game_screen.action
                self.sounds['music game'].stop()
                if self.configuration['sound'] == 'on':
                    self.sounds['music menu'].play(-1)
                continue
            elif action == 3:
                options_screen = OptionsScreen(self.screen, self.configuration, self.sounds)
                options_screen.start()
                action = options_screen.action
                continue
            elif action == 2:
                about_screen = AboutScreen(self.screen, self.sounds)
                about_screen.start()
                action = about_screen.action
                continue
            elif action == 4:
                break
            
            menu_screen.start()
            action = menu_screen.action
        
        pygame.quit()

#------------------------------------------------------------------------------

def main():
    """Main Function.
    """
    game = Game()
    game.start()

if __name__ == "__main__":
    main()
    sys.exit(0)
